#include "oyun_im.h"
#include <iostream> // for testing
#include <sys/mman.h>
using std::regex;
using std::regex_replace;

// 手写识别接口(实验室)
const string urlHandwriting = "http://sx.nmgoyun.com:9001";
// 手写识别接口(郭慧乔)
const string urlHandwritingQC = "http://oy.nmgoyun.com/api/handWriting/v2";
// 语音识别接口
const string urtVoice = "https://oy.nmgoyun.com/api/asr/v1";
// 输入法后台地址
const string serverAddress = "http://ime.nmgoyun.com:8080/IMEManage";

const string vowel_string = "aeiouqvE";
const string base_letter_string = "abcdefghijklmnopqrstuvwxyzNERCHLQZ";

inline bool char_in_string(char c, string s) {
    return (s.find(c) != string::npos);
}

bool is_special(char c) { return !char_in_string(c, base_letter_string); }

bool is_consonant(char c) { return !char_in_string(c, vowel_string); }

bool is_vowel(char c) { return char_in_string(c, vowel_string); }

string filter_special(string s) {
    s.erase(std::remove_if(s.begin(), s.end(), is_special), s.end());
    return s;
}

// get all consonant of the input string, but retain the first letter
string get_consonant(string s) {
    if (s.length() > 0) {
        s.erase(std::remove_if(s.begin() + 1, s.end(), is_vowel), s.end());
    }
    return s;
}

bool should_skip_vowel(const string &word) {
    for (int i = 1; i < word.length(); i++) {
        if (is_consonant(word[i]) && is_consonant(word[i - 1])) {
            return true;
        }
    }
    return false;
}

/**
 * 修改配置文件
 * @param item 配置项
 * @param state 配置项状态
 */
void IM_Config::set_config(const string &item, const string &state) {
    // 打开文件并将其读入内存
    string filename = CONFIG;
    ifstream file(filename);
    vector<string> lines;
    string line;
    while (std::getline(file, line)) {
        lines.push_back(line);
    }

    for (auto it = lines.begin(); it != lines.end();) {
        std::string::size_type colon1_pos = it->find(":");
        if (colon1_pos != std::string::npos) {
            std::string sub_str = it->substr(0, colon1_pos);
            if (sub_str == item) {
                *it = item + ":" + state;
                break;
            }
        }
        ++it;
    }
    file.close();
    // 将修改后的内容写回文件
    std::ofstream file_out(filename);
    for (const auto &line : lines) {
        file_out << line << '\n';
    }
    file_out.close();
}

OyunIM::OyunIM(IM_Config *config, const string &dictionary,
               const string &user_defined_word_list,
               const string &build_in_word_list, const string &gram2,
               const string &gram3, const string &quick_phrase_dir,
               const string &config_dir)
    : history1(0), history2(0), imConfig(config) {
    std::ifstream in_file(dictionary, std::ifstream::binary);
    if (!in_file) {
        std::cout << "loaded data from " << dictionary << "\n";
        d = hps::from_stream<IM_Data>(in_file);
        in_file.close();
    } else { // this path cannot be reached for installed IME.
        make_tree(user_defined_word_list, true);
        make_tree(build_in_word_list, false);
        save_dictionary();
    }
    make_gram(gram2, gram3);
    std::cout << "loaded " << d.word_list.size() << " words\n";
    std::cout << "dict root contains " << d.tree.tree.size()
              << " nodes, which are \n";
    for (auto &c : d.tree.tree) {
        std::cout << c.first << " ";
    }
    std::cout << "\n";
    std::cout << "loaded " << d.gram2.gram.size() << " 2-gram items; and "
              << d.gram3.gram.size() << " 3-gram items\n";

    // 读取快速短语文件
    std::ifstream file(QUICK_PHRASE_DIR);
    std::string line;
    while (std::getline(file, line)) {
        if (!line.empty() && line.at(0) != '#') {
            std::stringstream ss(line);
            std::string word;
            string phrase, abbr;
            int position;
            std::getline(ss, abbr, ':');
            std::getline(ss, phrase, ':');
            ss >> position;
            insert_quick_phrase(abbr, phrase, position);
        }
    }
    file.close();

    // 读取配置文件
    std::ifstream config_file(CONFIG);
    int i = 0;
    while (std::getline(config_file, line)) {
        std::stringstream ss(line);
        string config_item;
        int state;
        std::getline(ss, config_item, ':');
        ss >> state;
        switch (i) {
        case 0:
            imConfig->set_today_input(state);
            break;
        case 1:
            imConfig->set_total_input(state);
            break;
        case 2:
            imConfig->set_kpm(state);
            break;
        case 3:
            imConfig->set_rank(state);
            break;
        case 4:
            imConfig->set_font(state);
            break;
        case 5:
            imConfig->set_code_switch(state);
            break;
        case 6:
            imConfig->set_key_switch(state);
            break;
        case 7:
            imConfig->set_search_switch_vq(state);
            break;
        case 8:
            imConfig->set_search_switch_ou(state);
            break;
        case 9:
            imConfig->set_search_switch_td(state);
            break;
        case 10:
            imConfig->set_search_switch_hg(state);
            break;
        case 11:
            imConfig->set_search_switch_eE(state);
            break;
        case 12:
            imConfig->set_search_switch_wE(state);
            break;
        case 13:
            imConfig->set_search_switch_ngN(state);
            break;
        case 14:
            imConfig->set_search_switch_sixi(state);
            break;
        default:
            break;
        }
        i++;
    }
    config_file.close();
}

/**
 * 插入一个新的自定义短语
 * 加入新短语时前端是不是需要判1-9？
 * @param abbr 缩写
 * @param phrase 短语
 * @param position 显示位置
 * @return 插入状态
 */
bool OyunIM::insert_quick_phrase(const string &abbr, const string &phrase,
                                 int position) {
    if (imConfig->quick_phrase_index >= MAX_QUICK_PHRASE)
        return false;
    for (int i = 0; i < imConfig->quick_phrase_index; ++i) {
        if (imConfig->quick_phrase_list[i].phrase == phrase ||
            (imConfig->quick_phrase_list[i].abbr == abbr &&
             imConfig->quick_phrase_list[i].position == position))
            return false;
    }
    Phrase p(abbr, phrase, position);
    imConfig->quick_phrase_list[imConfig->quick_phrase_index] = p;
    imConfig->quick_phrase_index++;
    return true;
}

/**
 * 插入一个新的自定义短语到存储文件里
 * @param abbr 缩写
 * @param phrase 短语
 * @param position 显示位置
 * @return 插入状态
 */
bool OyunIM::insert_quick_phrase_in_file(const string &abbr,
                                         const string &phrase, int position) {
    string filename = QUICK_PHRASE_DIR;
    ofstream outfile;
    // 新增在文件后面接着写
    outfile.open(filename, std::ios::app);
    if (outfile.is_open()) {
        outfile << abbr << ":" << phrase << ":" << position << endl;
        outfile.close();
        return true;
    } else {
        return false;
    }

    return false;
}

/**
 * 第一页为0，page等于0获取全部短语
 * @param page
 * @return json短语
 */
string OyunIM::get_quick_phrase_by_page(int page) {
    std::ostringstream oss;
    oss << "{\"currentPage\":" << page;
    int total = imConfig->quick_phrase_index;
    int start = (page == 0) ? 0 : (page - 1) * 10;
    int end = (page == 0) ? total : page * 10;
    int count = std::min(end, total) - start;
    oss << ",\"total\":" << total << ",\"data\":[";
    for (int i = start; i < start + count; i++) {
        const auto &qp = imConfig->quick_phrase_list[i];
        oss << R"({"id":)" << i << R"(,"abbr":")" << qp.abbr
            << R"(","phrase":")" << qp.phrase << R"(","position":)"
            << qp.position << "},";
    }
    if (count > 0) {
        oss.seekp(-1, std::ios_base::end);
    }
    oss << "]}";

    return oss.str();
}
/**
 * 修改一条记录
 * 修改时直接把原先的记录删除，再新添加记录
 * @param new_abbr 新缩写
 * @param old_phrase 原短语
 * @param new_phrase 新短语
 * @param new_position 新位置
 * @return 修改状态
 */
bool OyunIM::modify_quick_phrase(const string &new_abbr,
                                 const string &old_phrase,
                                 const string &new_phrase, int new_position) {
    for (auto &i : imConfig->quick_phrase_list) {
        if (i.phrase == old_phrase) {
            i.position = new_position;
            strcpy(i.phrase, new_phrase.c_str());
            strcpy(i.abbr, new_abbr.c_str());
            return true;
        }
    }
    return false;
}
/**
 * 修改一条记录
 * 修改时直接把原先的记录删除，再新添加记录
 * @param new_abbr 新缩写
 * @param old_phrase 原短语
 * @param new_phrase 新短语
 * @param new_position 新位置
 * @return 修改状态
 */
bool OyunIM::modify_quick_phrase_in_file(const string &new_abbr,
                                         const string &old_phrase,
                                         const string &new_phrase,
                                         int new_position) {
    // 打开文件并将其读入内存
    string filename = QUICK_PHRASE_DIR;
    // TODO 文件路径需要修改，usr/local/share/fcitx/data目录下的文件需要修改吗？
    ifstream file(filename);
    vector<string> lines;
    string line;
    while (std::getline(file, line)) {
        lines.push_back(line);
    }

    for (auto it = lines.begin(); it != lines.end();) {
        std::string::size_type colon1_pos = it->find(":");
        std::string::size_type colon2_pos = it->find(":", colon1_pos + 1);
        if (colon2_pos != std::string::npos) {
            std::string sub_str =
                it->substr(colon1_pos + 1, colon2_pos - colon1_pos - 1);
            if (sub_str == old_phrase) {
                *it =
                    new_abbr + ":" + new_phrase + ":" + to_string(new_position);
                break;
            }
        }
        ++it;
    }
    file.close();
    // 将修改后的内容写回文件
    std::ofstream file_out(filename);
    for (const auto &line : lines) {
        file_out << line << '\n';
    }
    file_out.close();
    return true;
}
/**
 * 删除一个自定义短语
 * @param phrase 自定义短语
 * @return 删除状态
 */
bool OyunIM::remove_quick_phrase(const string &phrase) {
    for (int i = 0; i < imConfig->quick_phrase_index; ++i) {
        if (imConfig->quick_phrase_list[i].phrase == phrase) {
            for (int j = i; j < imConfig->quick_phrase_index; ++j) {
                imConfig->quick_phrase_list[j] =
                    imConfig->quick_phrase_list[j + 1];
            }
            imConfig->quick_phrase_index--;
            return true;
        }
    }
    return false;
}

/**
 * 把自定义短语从文件中删除
 * @param phrase 短语
 * @return 文件中删除状态
 */
bool OyunIM::remove_quick_phrase_in_file(const string &phrase) {
    //    char buf[256];
    //    if (getcwd(buf, sizeof(buf)) != NULL) {
    //        std::cout << "当前工作目录为: " << buf << std::endl;
    //    }
    // 打开文件并将其读入内存
    string filename = QUICK_PHRASE_DIR;
    // TODO 文件路径需要修改，usr/local/share/fcitx/data目录下的文件需要修改吗？
    ifstream file(filename);
    vector<string> lines;
    string line;
    while (std::getline(file, line)) {
        lines.push_back(line);
    }

    for (auto it = lines.begin(); it != lines.end();) {
        std::string::size_type colon1_pos = it->find(":");
        std::string::size_type colon2_pos = it->find(":", colon1_pos + 1);
        if (colon2_pos != std::string::npos) {
            std::string sub_str =
                it->substr(colon1_pos + 1, colon2_pos - colon1_pos - 1);
            if (sub_str == phrase) {
                it = lines.erase(it);
                break;
            }
        }
        ++it;
    }
    file.close();
    // 将修改后的内容写回文件
    std::ofstream file_out(filename);
    for (const auto &line : lines) {
        file_out << line << '\n';
    }
    file_out.close();
    return true;
}

/**
 * 获取abbr对应的短语列表
 * @param abbr 缩写
 * @return abbr对应的短语列表
 */
vector<Phrase> OyunIM::search_quick_phrase(const string &abbr) const {
    std::vector<Phrase> res;
    for (int i = 0; i < imConfig->quick_phrase_index; ++i) {
        if (imConfig->quick_phrase_list[i].abbr == abbr) {
            res.push_back(imConfig->quick_phrase_list[i]);
        }
    }
    return res;
}

void OyunIM::save_dictionary() {
    std::ofstream out_file(OYUN_DICT, std::ofstream::binary);
    hps::to_stream(d, out_file);
    out_file.close();
}

bool word_list_cmp(const Word &a, const Word &b) { return a.widx < b.widx; }

void OyunIM::make_tree(const string &file_name, bool user_defined) {
    std::ifstream file(file_name);
    string line, word, latin;
    int frequency, idx;
    if (file) {
        while (getline(file, line)) {
            std::istringstream lin(line);
            lin >> idx >> word >> latin >> frequency;
            // instert item into word list
            d.word_list.emplace_back(
                user_defined, get_consonant(latin).length(), frequency,
                is_consonant(latin[latin.length() - 1]), word, latin, idx);
            // insert item into tree
            WordTree *curr = &d.tree;
            for (char &c : latin) {
                if (curr->tree.count(c) == 0) {
                    curr->tree[c] = WordTree();
                }
                curr = &(curr->tree[c]);
            }
            curr->words.push_back(idx);
        }
        // 根据idx排序word_list，并且idx必须是连续的
        sort(d.word_list.begin(), d.word_list.end(), word_list_cmp);
        for (int i = 0; i < d.word_list.size(); ++i) {
            if (d.word_list[i].widx != i) {
                auto it = d.word_list.begin() + i;
                d.word_list.emplace(it, true, 0, 0, 0, "", "", i);
            }
        }
    } else {
        throw std::runtime_error(file_name + "is not found.");
    }
}

vector<string> OyunIM::extend_query(string s) {
    vector<string> tree;
    for (char &c : s) {
        tree.emplace_back(1, c);
    }
    for (unsigned i = 0; i < s.length(); i++) {

        if (imConfig->get_search_switch_sixi() && i < (s.length() - 1) &&
            s[i] == 'x' && s[i + 1] == 'i') { // si->xi
            tree[i] = "sx";
        } else if (imConfig->get_search_switch_vq() &&
                   (s[i] == 'q' || s[i] == 'v')) {
            tree[i] = "qv";
        } else if (imConfig->get_search_switch_ou() &&
                   (s[i] == 'o' || s[i] == 'u')) {
            tree[i] = "ou";
        } else if (imConfig->get_search_switch_td() &&
                   (s[i] == 't' || s[i] == 'd')) {
            tree[i] = "td";
        } else if (imConfig->get_search_switch_eE() &&
                   (s[i] == 'e' || s[i] == 'E')) {
            tree[i] = "eE";
        } else if (imConfig->get_search_switch_wE() &&
                   (s[i] == 'w' || s[i] == 'E')) {
            tree[i] = "wE";
        } else if (imConfig->get_search_switch_hg() &&
                   (s[i] == 'h' || s[i] == 'g')) {
            tree[i] = "hg";
        } else if (imConfig->get_search_switch_ngN() && i < (s.length() - 1) &&
                   s[i] == 'n' && s[i + 1] == 'g') { // ng->N
            tree[i] = "ngN";
        }
    }
    return tree;
}

// a filter rule is add here, the candidate must not longer the the query
// more than 2 consonant.
set<unsigned> OyunIM::search(const vector<string> &query, int consonant_length,
                             bool skip_vowel) {
    // mutil-path forward search
    // TODO: refactor by operator redefining.
    vector<WordTree *> item = {&d.tree};
    vector<WordTree *> curr;
    for (auto x = query.begin(); x != query.end(); x++) {
        curr = item;
        item.clear();
        for (auto c = (*x).begin(); c != (*x).end(); c++) {
            for (auto d = curr.begin(); d != curr.end(); d++) {
                if ((*d)->tree.count(*c) > 0) {
                    item.push_back(&((*d)->tree[*c]));
                }
            }
        }
        if (skip_vowel) {
            curr = item;
            for (auto c = vowel_string.begin(); c != vowel_string.end(); c++) {
                for (auto d = curr.begin(); d != curr.end(); d++) {
                    if ((*d)->tree.count(*c) > 0) {
                        // If the current node contains a vowel, skip over it and proceed
                        // to the next level of the tree.
                        if (is_vowel(*c) && !(*d)->tree[*c].tree.empty()) {
                            for (auto e = (*d)->tree[*c].tree.begin(); e != (*d)->tree[*c].tree.end(); e++) {
                                item.push_back(&(e->second));
                            }
                        }
                        item.push_back(&((*d)->tree[*c]));
                        
                    }
                }
            }
        }
        //  std::cout << "query: " << (*x) << " obtained " << item.size() << " nodes\n";
    }
    // perdicte from curret end, limited it too long
    set<unsigned> res;
    int i = 0, c;
    double current_consonant_length = consonant_length; // roughly gauss
    while (i < item.size()) {
        //        std::cout << "current nodes contains " <<
        //        item[i]->words.size()
        //                  << "words ";
        //        std::cout << "and " << item[i]->tree.size() << " trees.\n";
        for (auto x = item[i]->words.begin(); x != item[i]->words.end(); x++) {
            c = d.word_list[*x].consonant_length -
                d.word_list[*x].end_with_consonant;
            if (c <
                (consonant_length + 2)) { // TODO: hand code rule, update later
                res.insert(*x);
            }
            current_consonant_length *= 0.9; // avarge by x_t*0.9 + x_(t-1)*0.1
            current_consonant_length += 0.1 * c;
        }
        if (current_consonant_length >
            consonant_length + 2 + 1) { // give one more changes
            break;
        }
        for (auto c = item[i]->tree.begin(); c != item[i]->tree.end(); c++) {
            item.push_back(&(c->second));
        }
        i++; // while loop counter
    }
    return res;
}

std::map<char, string> latin2mn = {
    {97, "\u1820"},  {101, "\u1821"}, {105, "\u1822"}, {113, "\u1823"},
    {118, "\u1824"}, {111, "\u1825"}, {117, "\u1826"}, {69, "\u1827"},
    {110, "\u1828"}, {78, "\u1829"},  {98, "\u182A"},  {112, "\u182B"},
    {104, "\u182C"}, {103, "\u182D"}, {109, "\u182E"}, {108, "\u182F"},
    {115, "\u1830"}, {120, "\u1831"}, {116, "\u1832"}, {100, "\u1833"},
    {99, "\u1834"},  {106, "\u1835"}, {121, "\u1836"}, {114, "\u1837"},
    {119, "\u1838"}, {102, "\u1839"}, {107, "\u183A"}, {75, "\u183B"},
    {67, "\u183C"},  {122, "\u183D"}, {72, "\u183E"},  {82, "\u183F"},
    {76, "\u1840"},  {90, "\u1841"},  {81, "\u1842"},  {38, "\u180A"},
    {39, "\u180B"},  {34, "\u180C"},  {96, "\u180D"},  {95, "\u180E"},
    {45, "\u202F"},  {94, "\u200C"},  {42, "\u200D"},  {92, "\u1801"},
    {44, "\u1802"},  {46, "\u1803"},  {58, "\u1804"},  {36, "\u2014"},
    {60, "\u300A"},  {62, "\u300B"},  {91, "\u3014"},  {93, "\u3015"},
    {123, "\u3008"}, {125, "\u3009"}};

string do_latin2mn(string query, bool code_switch) {
    //    国标编码
    //    1.格后缀里词首的180E（MVS）替换成202F（NNBSP）；
    //    2.候选词包含xi且长度大于等于3的话把xi替换成si；
    string res;
    if (!code_switch) {
        size_t pos = query.find("xi");
        if (pos != string::npos && query.length() >= 3) {
            query.replace(pos, 2, "si");
        }
    }
    for (char c : query) {
        if (latin2mn.count(c) > 0) {
            if (!code_switch && c == '_')
                res += "\u202F";
            else
                res += latin2mn[c];
        } else {
            res += c;
        }
    }
    return res;
}



std::map<string, char> mn2latin = {
    {"\u1820", 97},  {"\u1821", 101}, {"\u1822", 105}, {"\u1823", 113},
    {"\u1824", 118}, {"\u1825", 111}, {"\u1826", 117}, {"\u1827", 69},
    {"\u1828", 110}, {"\u1829", 78},  {"\u182A", 98},  {"\u182B", 112},
    {"\u182C", 104}, {"\u182D", 103}, {"\u182E", 109}, {"\u182F", 108},
    {"\u1830", 115}, {"\u1831", 120}, {"\u1832", 116}, {"\u1833", 100},
    {"\u1834", 99},  {"\u1835", 106}, {"\u1836", 121}, {"\u1837", 114},
    {"\u1838", 119}, {"\u1839", 102}, {"\u183A", 107}, {"\u183B", 75},
    {"\u183C", 67},  {"\u183D", 122}, {"\u183E", 72},  {"\u183F", 82},
    {"\u1840", 76},  {"\u1841", 90},  {"\u1842", 81},  {"\u180A", 38},
    {"\u180B", 39},  {"\u180C", 34},  {"\u180D", 96},  {"\u180E", 95},
    {"\u202F", 45},  {"\u200C", 94},  {"\u200D", 42},  {"\u1801", 92},
    {"\u1802", 44},  {"\u1803", 46},  {"\u1804", 58},  {"\u2014", 36},
    {"\u300A", 60},  {"\u300B", 62},  {"\u3014", 91},  {"\u3015", 93},
    {"\u3008", 123}, {"\u3009", 125}, {"\u202F", 95}};
/**
 * convert mongolian string to latin,but only use for x86 arch
 * @param query
 * @param code_switch
 * @return
 */
string do_mn2latin(const string &query, bool code_switch) {
    string res = "";
    string key = "";
    for (auto c = query.begin(); c != query.end(); c++) {
        if (*c < 0) {
            key += *c;
            if (key.length() == 3) { // unicode's length is 3
                if (mn2latin.count(key) > 0) {
                    res += mn2latin[key];
                    key = "";
                } else {
                    res += key;
                    key = "";
                }
            }
        } else {
            res += *c;
        }
    }
    // TODO 国标编码:转换为拉丁的时候si要倒转成xi吗？
    if (!code_switch) {
        size_t pos = res.find("si");
        if (pos != string::npos && res.length() >= 3) {
            res.replace(pos, 2, "xi");
        }
    }
    return res;
}

string OyunIM::get_current_word_latin(const string &query) {
    return do_mn2latin(query, imConfig->get_code_switch());
}

void OyunIM::set_stub(const string &query) {
    word_stub.user_defined = true;
    word_stub.frequency = 0;
    word_stub.word =
        do_latin2mn(query, imConfig->get_code_switch()); // 国标编码需要特殊处理
    word_stub.latin = query;
    word_stub.consonant_length = get_consonant(filter_special(query)).length();
    word_stub.end_with_consonant = is_consonant(filter_special(query)[0]);
    word_stub.widx = (unsigned)(-1);
}

bool word_cmp(const Word &a, const Word &b) {
    if (a.user_defined != b.user_defined) {
        return a.user_defined < b.user_defined;
    }
    if (a.consonant_length != b.consonant_length) { // the shorter the better
        return a.consonant_length < b.consonant_length;
    }
    return a.frequency > b.frequency;
}

bool phrase_cmp(const Phrase &a, const Phrase &b) {
    return a.position < b.position;
}

string key_transform(string s, bool state) {
    if (state){
        return s;
    }

    // 蒙科立布局需要更改输入串
    transform(s.begin(), s.end(), s.begin(), [](char c) {
        if (c == 'q')
            return 'c';
        else if (c == 'c')
            return 'q';
        else
            return c;
    });
    replace(s.begin(), s.end(), 'N', ' ');
    replace(s.begin(), s.end(), ';', 'N');
    return s;
}

vector<Word> OyunIM::do_query(const string &query, int flag) {
    vector<Word> words;
    // if flag = 1, use digit hot key to select lattice_suffixs
    if (flag) {
        return use_digit_hotkey_search_lattice_suffix((int)query[0] - '0');
    }
    // if flag=0,select lattice_suffixs and candidate words
    else {
        // 格后缀处理
        if ((!imConfig->get_code_switch() && query[0] == '-') || (imConfig->get_code_switch() && query[0] == '_')) {
            return search_lattice_suffix(query.substr(1));
        }
        // 1: 剔除特数字符和根据键盘布局替换拉丁
        string query_str =
            filter_special(key_transform(query, imConfig->get_key_switch()));
        // use for search step 3
        int consonant_length = get_consonant(query_str).length();
        bool skip_vowel = should_skip_vowel(query_str);
        // 2: extend the query
        vector<string> query_list = extend_query(query_str);
        // 3: search in dictionary
        set<unsigned> candidate =
            search(query_list, consonant_length, skip_vowel);
        // pick the exact match at the first place before sorting
        set_stub(key_transform(query, imConfig->get_key_switch()));
        words.insert(words.begin(), word_stub);

        // 4: sort the candidates
        for (auto &c : candidate) {
            words.push_back(d.word_list[c]);
        }

        sort(words.begin() + 1, words.end(), word_cmp);
        // 5:用户自定义短语查询
        vector<Phrase> phrases = search_quick_phrase(query);
        sort(phrases.begin(), phrases.end(),
             phrase_cmp); // 按候选词显示位置排序
        for (int i = 0; i < phrases.size(); i++) {
            Word temp(
                do_latin2mn(phrases[i].phrase, imConfig->get_code_switch()), phrases[i].phrase);
            if (phrases[i].position <= words.size()) { // 防止数组越界
                words.insert(words.begin() + phrases[i].position, temp);
            } else {
                words.push_back(temp);
            }
        }
        return words;
    }
}

vector<unsigned> OyunIM::lookup(const string &query) {
    vector<unsigned> result;
    WordTree *curr = &d.tree;
    for (auto c = query.begin(); c != query.end(); c++) {
        if (curr->tree.count(*c) == 0) {
            return result; // max - 1, very large
        } else {
            curr = &(curr->tree[*c]);
        }
    }
    if (curr->words.size() == 0) {
        return result;
    }
    return curr->words;
}

// 辅音结尾 阳 0
// 元音结尾 阳 1
// 辅音结尾 阴 2
// 元音结尾 阴 3
// 以n结尾 阳 5
// 以n结尾 阴 4
// 以 m、l、N结尾  阳 7
// 以 m、l、N结尾 阴 6

vector<vector<string>> lattice_suffix_map = {
    {"_vn", "_tv", "_i", "_iyar", "_aca", "_tai",
     "_vd", "_iyan", "_tagan", "_lvg_a", "_tvr",
     "_naihan", "_yvgan", "_acagan", "_ugei", "_nar"},
    {"_yin", "_dv", "_yi", "_bar", "_aca", "_tai",
     "_nvgvd", "_ban", "_dagan", "_lvg_a", "_dvr",
     "_naihan", "_yvgan", "_acagan", "_ugei", "_nar"},
    {"_vn", "_tv", "_i", "_iyar", "_aca", "_tai",
     "_vd", "_iyan", "_tegen", "_luge", "_tvr",
     "_neihen", "_yugen", "_ecegen", "_ugei", "_nar"},
    {"_yin", "_dv", "_yi", "_bar", "_aca", "_tai",
     "_nugud", "_ben", "_degen", "_luge", "_dur",
     "_neihen", "_yugen", "_ecegen", "_ugei", "_nar"},
    {"_v", "_dv", "_i", "_iyar", "_aca", "_tai",
     "_nugud", "_iyan", "_degen", "_luge", "_dur",
     "_neihen", "_yugen", "_ecegen", "_ugei", "_nar"},
    {"_v", "_dv", "_i", "_iyar", "_aca", "_tai",
     "_nvgvd", "_iyan", "_dagan", "_lvg_a", "_dvr",
     "_naihen", "_yvgan", "_acagan", "_ugei", "_nar"},
    {"_vn", "_dv", "_i", "_iyar", "_aca", "_tai",
     "_vd", "_iyan", "_nugud", "_ben", "_degen",
     "_luge", "_dur", "_neihen", "_yugen", "_ecegen",
     "_ugei", "_nar"},
    {"_vn", "_dv", "_i", "_iyar", "_aca", "_tai",
     "_vd", "_iyan", "_dagan", "_lvg_a", "_dvr",
     "_naihan", "_yvgan", "_acagan", "_ugei", "_nar"},
    {"_yin",    "_vn",     "_v",     "_mini",
     "_ni",     "_cini",   "_dv",    "_tv",
     "_yi",     "_i",      "_bar",   "_iyar",
     "_aca",    "_tai",    "_nvgvd", "_nugud",
     "_vd",     "_ban",    "_dehi",  "_iyan",
     "_dagan",  "_tagan",  "_degen", "_tegen",
     "_lvg_a",  "_luge",   "_dvr",   "_tvr",
     "_naihan", "_neihen", "_yvgan", "_yugen",
     "_acagan", "_ecegen", "_ugei",  "_nar"},
};

vector<vector<string>> lattice_suffix_map1 = {
    {"_mini", "_cini", "_ni"},                     // 0
    {"_yin", "_vn", "_un", "_v", "_u"},  // 1
    {"_yi", "_i"},                                      // 2,
    {"_dv", "_tv", "_du", "_tu"},             // 3
    {"_bar", "_iyar", "_ber", "_iyer"},       // 4
    {"_ban", "_iyan", "_ben", "_iyen"},       // 5
    {"_dagan", "_tagan", "_degen", "_tegen"}, // 6
    {"_vd", "_ud", "_nvgvd", "_nugud", "_nar",
     "_ner"},                                            // 7
    {"_dahi", "_tahi", "_dehi", "_tehi"}, // 8
    {"_aca", "_ece"},                               // 9
};

vector<Word>
OyunIM::use_digit_hotkey_search_lattice_suffix(const int index) const {
    vector<Word> lattice_suffixs;
    for (const auto &i : lattice_suffix_map1[index]) {
        Word tmp(do_latin2mn(i, imConfig->get_code_switch()), i);
        lattice_suffixs.push_back(tmp);
    }
    return lattice_suffixs;
}

vector<Word> OyunIM::search_lattice_suffix(const string &s) const {
    vector<Word> lattice_suffixs;
    for (auto &i : lattice_suffix_map[8]) {
        if (i.compare(1, s.length(), s) == 0) {
            Word tmp(do_latin2mn(i, imConfig->get_code_switch()), i);
            lattice_suffixs.push_back(tmp);
        }
    }
    return lattice_suffixs;
}

vector<string> OyunIM::lattice_suffix_recommendation(const string &s) {
    string str = s;

    if (str == "-" || str == "_") {   // NSP
        return lattice_suffix_map[8]; // return all
    }
    int index = 0; // F-0
    char last_char = str[str.length() - 1];
    if (is_vowel(last_char)) {
        index = 1; // Y-1
    }
    if (last_char == 'n') {
        index = 5; // F-2
    }
    if (char_in_string(last_char, "mlN")) {
        index = 7; // F-4
    }
    bool trueYinYang = false;
    for (int i = str.length() - 1; i >= 0; i--) {
        if (is_vowel(str[i])) {
            if (char_in_string(str[i], "aqv")) { // a,q,v 阳性
                trueYinYang = true;
                break;
            } else if (char_in_string(str[i], "eou")) {
                switch (index) {
                case 5:
                case 7:
                    index--;
                    break;
                default:
                    index += 2; // F-Yin-3-Y-Yin-2
                }
                trueYinYang = true;
                break;
            }
        }
    }
    if (!trueYinYang) {
        switch (index) {
        case 5:
        case 7:
            index--;
            break;
        default:
            index += 2; // F-Yin-3-Y-Yin-2
        }
    }
    return lattice_suffix_map[index];
}

std::map<string, string> suffix_map = {{"-vn", "*vn"},
                                       {"-v'n", "*vn"},
                                       {"-v'", "*v"},
                                       {"-v", "*v"},
                                       {"-u", "*u"},
                                       {"-u\"", "*u"},
                                       {"-un", "*un"},
                                       {"-u'n", "*un"},
                                       {"-i", "*i"},
                                       {"-i'", "*i"},
                                       {"-iyan", "*iy'an"},
                                       {"-i'y'an", "*iy'an"},
                                       {"-iyen", "*iy'en"},
                                       {"-i'y'en", "*iy'en"},
                                       {"-vd", "*vd"},
                                       {"-v'd", "*vd"},
                                       {"-ud", "*ud"},
                                       {"-u'd", "*ud"},
                                       {"-iyer", "*iy'er"},
                                       {"-i'y'er", "*iy'er"},
                                       {"-iyar", "*iy'ar"},
                                       {"-i'y'ar", "*iy'ar"},
                                       {"-lu`ge", "-lvge"},
                                       {"-yu`gen", "-yvgen"},
                                       {"-neihen", "-ney'ihen"},
                                       {"-naihan", "-nay'ihan"}};

inline void apply_rule(string &s, const string &from, const string &to) {
    s = std::regex_replace(s, std::regex(from), to);
}

string single_process(string w) {
    apply_rule(w, "^vv$", "*vv");
    apply_rule(w, "^uu$", "*uu");
    apply_rule(w, "^d([aeiouqvE])$", "d'$1");
    apply_rule(w, "^d([aeiouqvE])['`\"]$", "d'$1");
    apply_rule(w, "^[nbphgmlsxtdcjyrwHRLZzC][uoqv]", "$0'");
    apply_rule(w, "yu`m", "yvm");
    return w;
}

string not_single_process(string s) {
    if (s == "naima") {
        return s;
    }
    // xi --> si
    apply_rule(s, "xi", "si");
    // [sd]g[aqv] --> h
    apply_rule(s, "([sd])g([aqv])", "$1h$2");
    // [aeqv]i[a-z, A-Z] -- > [aeqv]y'i
    apply_rule(s, "([aeqv])i([a-zA-Z])", "$1y'i$2");
    // 【两个或两个以上字符】+【u/o】+【i】+【大于0数量字符】的时候需要把【i】替换成【y’i】
    apply_rule(s, "([a-zA-Z][a-zA-Z])([uo])i([a-zA-Z])", "$1$2y'i$3");
    // n"-->n'
    apply_rule(s, "n\"", "n'");
    // nai`ma -- > naima
    apply_rule(s, "nai`ma", "naima");
    // d'u[所有字母]（u后面不是其他控制符） --> d'u'[所有字母](新增-2019.8.27)
    apply_rule(s, "d'u([a-zA-Z])", "d'u'$1");
    // s和d后面接g_a的情况下，将g_a变成g'_a,并且a为字符串结尾(2020.5.6新增)
    apply_rule(s, "([sd])g_a$", "$1g'_a");
    // isg_a结尾，替换成ish_a结尾（2020.5.8新增）
    apply_rule(s, "isg_a$", "ish_a");
    // gi结尾单词，将gi替换成hi（2020.5月10新增）
    apply_rule(s, "gi$", "hi");
    // sig'v -- > sigv    (2021.1.14新增)
    apply_rule(s, "sig'v", "sigv");
    // 以ig结尾的长度为3的单词，如果开头不是a/q/v的话把g替换成g”（2021.1.16新增）
    apply_rule(s, "^([^aqv])ig$", "$1ig\"");
    // 以ed’l开头的单词，把d’替换成d”（2021.1.16新增）
    apply_rule(s, "^ed'l", "ed\"l");
    // 以w_a结尾的单词，把w替换成w’（2021.1.16新增）
    apply_rule(s, "w_a$", "w'_a");
    return s;
}

string asr_word_postprocess(string w) {
    if (w.length() == 0) {
        return w;
    } else if (w[0] == '-') {
        if (suffix_map.count(w) > 0) {
            return suffix_map[w];
        }
    } else {
        return not_single_process(single_process(w));
    }
    return w;
}

string OyunIM::asr_postprocess(string s) {
    string res = "";
    string word = "";
    for (int i = 0; i < s.length(); i++) {
        if (char_in_string(s[i], " ?!:.,;")) {
            res += asr_word_postprocess(word) + s[i];
            word = "";
        } else {
            word += s[i];
        }
    }
    res += asr_word_postprocess(word);
    return res;
}

void OyunIM::make_gram(const string &gram2, const string &gram3) {
    std::ifstream file(gram2);
    string line;
    int w1, w2, w3;
    int frequency;
    if (file) {
        while (getline(file, line)) {
            std::istringstream lin(line);
            lin >> w1 >> w2 >> frequency;
            // instert item into gram list
            d.gram2.gram[w1].push_back(GramItem(w2, frequency));
        }
        d.gram2.sort();
    } else {
        throw std::runtime_error(gram2 + "is not found.");
    }
    std::ifstream file3(gram3);
    if (file3) {
        while (getline(file3, line)) {
            std::istringstream lin(line);
            lin >> w1 >> w2 >> w3 >> frequency;
            // instert item into gram list
            d.gram3.gram[w1].gram[w2].push_back(GramItem(w3, frequency));
        }
        d.gram3.sort();
    } else {
        throw std::runtime_error(gram3 + "is not found.");
    }
}

vector<Word> OyunIM::gram_recommendation() {
    vector<GramItem> g2 = d.gram2.gram[history2];
    vector<GramItem> g3 = d.gram3.gram[history1].gram[history2];
    vector<Word> res;
    for (auto &i : g3) {
        res.push_back(d.word_list[i.word]);
    }
    for (auto &i : g2) {
        res.push_back(d.word_list[i.word]);
    }
    return res;
}

vector<Word> OyunIM::query_next(const string &query) {
    vector<Word> res;
    vector<unsigned> ids;
//    string latin = do_mn2latin(query, imConfig->get_code_switch());
    string latin = query;
    if (latin.back() == ' ') {
        latin.pop_back();
    }
    if (word_stub.word == query) { // this is a user-defined word
        unsigned widx = d.word_list.size();
        word_stub.widx = widx;
        d.word_list.push_back(word_stub);

        WordTree *curr = &d.tree;
        for (char &c : latin) {
            if (curr->tree.count(c) == 0) {
                curr->tree[c] = WordTree();
            }
            curr = &(curr->tree[c]);
        }
        curr->words.push_back(widx);
    } else {
        ids = lookup(latin);
        if (ids.empty()) {
            return res; // empty
        }
    }
    history1 = history2;
    for (auto &id : ids) {
        d.word_list[id].frequency++;
        history2 = id;
        vector<Word> tmp = gram_recommendation();
        res.insert(res.end(), tmp.begin(), tmp.end());
    }
    return res;
}

IM_Config *create_shared_memory1() {
    return (IM_Config *)mmap(NULL, sizeof(IM_Config), PROT_READ | PROT_WRITE,
                             MAP_SHARED | MAP_ANONYMOUS, -1, 0);
}
int main(int argc, char const *argv[]) {
    OyunIM *ime = new OyunIM(create_shared_memory1());
//    ime->imConfig->set_key_switch(false);
    string query = "_";
    vector<Word> words = ime->do_query(query, 0);
    std::cout << query << std::endl;
    for (auto &word : words) {
        std::cout << do_mn2latin(word.word, true) << "   "
                  << word.consonant_length << "   " << word.frequency
                  << std::endl;
    }
    return 0;
}